/**
 * @file
 * SNTP client module
 *
 * This is simple "SNTP" client for the lwIP raw API.
 * It is a minimal implementation of SNTPv4 as specified in RFC 4330.
 *
 * For a list of some public NTP servers, see this link :
 * https://support.ntp.org/bin/view/Servers/NTPPoolServers
 *
 * @todo:
 * - set/change servers at runtime
 * - complete SNTP_CHECK_RESPONSE checks 3 and 4
 * - support brcast/multicast mode?
 */

/*
 * Copyright (c) <2013-2015>, <Huawei Technologies Co., Ltd>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 *
 * Author: Simon Goldschmidt (lwIP raw API part)
 */

/**********************************************************************************
 * Notice of Export Control Law
 * ===============================================
 * Huawei LiteOS may be subject to applicable export control laws and regulations, which
 * might include those applicable to Huawei LiteOS of U.S. and the country in which you
 * are located.
 * Import, export and usage of Huawei LiteOS in any manner by you shall be in compliance
 * with such applicable export control laws and regulations.
 **********************************************************************************/
#include "lwip/sys.h"

#include "lwip/opt.h"

#include "lwip/sntp.h"
#include "lwip/timeouts.h"
#include "lwip/sys.h"
#include "lwip/udp.h"
#include "lwip/dns.h"
#include "lwip/ip_addr.h"
#include "lwip/pbuf.h"
#include "lwip/tcpip.h"
#include "lwip/sockets.h"
#include <string.h>
#include <time.h>

#if LWIP_SNTP

/* Sanity check:
 * Define this to
 * - 0 to turn off sanity checks (default; smaller code)
 * - >= 1 to check address and port of the response packet to ensure the
 *        response comes from the server we sent the request to.
 * - >= 2 to check returned Originate Timestamp against Transmit Timestamp
 *        sent to the server (to ensure response to older request).
 * - >= 3 @todo: discard reply if any of the L_I, Stratum, or Transmit Timestamp
 *        fields is 0 or the Mode field is not 4 (unicast) or 5 (brcast).
 * - >= 4 @todo: to check that the Root Delay and Root Dispersion fields are each
 *        greater than or equal to 0 and less than infinity, where infinity is
 *        currently a cozy number like one second. This check avoids using a
 *        server whose synchronization source has expired for a very long time.
 */
#ifndef SNTP_CHECK_RESPONSE
#define SNTP_CHECK_RESPONSE         0
#endif

/* SNTP macro to get system time, used with SNTP_CHECK_RESPONSE >= 2
 * to send in request and compare in response.
 */
#ifndef SNTP_GET_SYSTEM_TIME
#define SNTP_GET_SYSTEM_TIME(sec, us)     do { (sec) = 0; (us) = 0; } while(0)
#endif

/* the various dbg levels for this file */
#define SNTP_DEBUG_TRACE        (SNTP_DEBUG | LWIP_DBG_TRACE)
#define SNTP_DEBUG_STATE        (SNTP_DEBUG | LWIP_DBG_STATE)
#define SNTP_DEBUG_WARN         (SNTP_DEBUG | LWIP_DBG_LEVEL_WARNING)
#define SNTP_DEBUG_WARN_STATE   (SNTP_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
#define SNTP_DEBUG_SERIOUS      (SNTP_DEBUG | LWIP_DBG_LEVEL_SERIOUS)

#define SNTP_ERR_KOD                1

/* SNTP protocol defines */
#define SNTP_MSG_LEN                48

#define SNTP_OFFSET_LI_VN_MODE      0
#define SNTP_LI_MASK                0xC0
#define SNTP_LI_NO_WARNING          0x00
#define SNTP_LI_LAST_MINUTE_61_SEC  0x01
#define SNTP_LI_LAST_MINUTE_59_SEC  0x02
#define SNTP_LI_ALARM_CONDITION     0x03 /* (clock not synchronized) */

#define SNTP_VERSION_MASK           0x38
#define SNTP_VERSION                (4/* NTP Version 4*/<<3)

#define SNTP_MODE_MASK              0x07
#define SNTP_MODE_CLIENT            0x03
#define SNTP_MODE_SERVER            0x04
#define SNTP_MODE_BROADCAST         0x05

#define SNTP_OFFSET_STRATUM         1
#define SNTP_STRATUM_KOD            0x00

#define SNTP_OFFSET_ORIGINATE_TIME  24
#define SNTP_OFFSET_RECEIVE_TIME    32
#define SNTP_OFFSET_TRANSMIT_TIME   40

/* number of seconds between 1900 and 1970 */
#define DIFF_SEC_1900_1970         (2208988800UL)

struct api_sntp_msg {
  int server_num;
  char **sntp_server;
  struct timeval *sntp_time;
  sys_sem_t cb_completed;
  err_t err;
};


/*
 * SNTP packet format (without optional fields)
 * Timestamps are coded as 64 bits:
 * - 32 bits seconds since Jan 01, 1970, 00:00
 * - 32 bits seconds fraction (0-padded)
 * For future use, if the MSB in the seconds part is set, seconds are based
 * on Feb 07, 2036, 06:28:16.
 */
PACK_STRUCT_BEGIN
struct sntp_msg {
 PACK_STRUCT_FIELD(u8_t           li_vn_mode);
 PACK_STRUCT_FIELD(u8_t           stratum);
 PACK_STRUCT_FIELD(u8_t           poll);
 PACK_STRUCT_FIELD(u8_t           precision);
 PACK_STRUCT_FIELD(u32_t          root_delay);
 PACK_STRUCT_FIELD(u32_t          root_dispersion);
 PACK_STRUCT_FIELD(u32_t          reference_identifier);
 PACK_STRUCT_FIELD(u32_t          reference_timestamp[2]);
 PACK_STRUCT_FIELD(u32_t          originate_timestamp[2]);
 PACK_STRUCT_FIELD(u32_t          receive_timestamp[2]);
 PACK_STRUCT_FIELD(u32_t          transmit_timestamp[2]);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END

/* function prototypes */
LWIP_STATIC void sntp_request(void *arg);

/* The UDP pcb used by the SNTP client */
static struct udp_pcb* sntp_pcb = NULL;
/* Addresses of servers */
static char **sntp_server_addresses;
static u8_t sntp_current_server;
static int sntp_num_servers;

static struct api_sntp_msg *sntp_msg;
#if SNTP_RETRY_TIMEOUT_EXP
/*lint -e773*/
#define SNTP_RESET_RETRY_TIMEOUT() sntp_retry_timeout = SNTP_RETRY_TIMEOUT
/*lint +e773*/

static u8_t sntp_retry_count;

/* Retry time, initialized with SNTP_RETRY_TIMEOUT and doubled with each retry. */
static u32_t sntp_retry_timeout;
#else /* SNTP_RETRY_TIMEOUT_EXP */
#define SNTP_RESET_RETRY_TIMEOUT()
#define sntp_retry_timeout SNTP_RETRY_TIMEOUT
#endif /* SNTP_RETRY_TIMEOUT_EXP */

#if SNTP_CHECK_RESPONSE >= 1
/* Saves the last server address to compare with response */
static ip_addr_t sntp_last_server_address;
#endif /* SNTP_CHECK_RESPONSE >= 1 */

#if SNTP_CHECK_RESPONSE >= 2
/* Saves the last timestamp sent (which is sent back by the server)
 * to compare against in response */
static u32_t sntp_last_timestamp_sent[2];
#endif /* SNTP_CHECK_RESPONSE >= 2 */

/* Stop sntp module */
LWIP_STATIC void sntp_stop(void);
LWIP_STATIC void sntp_process(u32_t *receive_timestamp, u32_t receive_timestamp_size);
LWIP_STATIC void sntp_initialize_request(struct sntp_msg *req);
LWIP_STATIC void sntp_retry(void* arg);
LWIP_STATIC void sntp_try_next_server(void* arg);
LWIP_STATIC void sntp_recv(void *arg, struct udp_pcb* pcb, struct pbuf *p, ip_addr_t *addr, u16_t port);
LWIP_STATIC void sntp_send_request(const ip_addr_t *server_addr);
#if SNTP_SERVER_DNS
LWIP_STATIC void sntp_dns_found(const char* hostname, const ip_addr_t *ipaddr, u32_t count, void *arg);
#endif

/*
 * SNTP processing of received timestamp
 */
LWIP_STATIC void
sntp_process(u32_t *receive_timestamp, u32_t receive_timestamp_size)
{
  /* convert SNTP time (1900-based) to unix GMT time (1970-based)
   * @todo: if MSB is 1, SNTP time is 2036-based!
   */
  time_t t;

  if (receive_timestamp_size != SNTP_RECEIVE_TIME_SIZE) {
    return;
  }

  t = (time_t)(ntohl(receive_timestamp[0]) - DIFF_SEC_1900_1970);

#if SNTP_CALC_TIME_US
  u32_t us = ntohl(receive_timestamp[1]) / 4295;
  SNTP_SET_SYSTEM_TIME_US(t, us);
  /* display local time from GMT time */
  LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_process: %s, %"U32_F" us", ctime(&t), us));

#else /* SNTP_CALC_TIME_US */

  /* change system time and/or the update the RTC clock */
  SNTP_SET_SYSTEM_TIME(t);
  /* display local time from GMT time */
  LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_process: %s", ctime(&t)));
#endif /* SNTP_CALC_TIME_US */
  if(sntp_msg->sntp_time != NULL) {
    sntp_msg->sntp_time->tv_sec = (long)t;
#if SNTP_CALC_TIME_US
    sntp_msg->sntp_time->tv_usec = (long)us;
#else
    sntp_msg->sntp_time->tv_usec = 0;
#endif
  }
}

/*
 * Initialize request struct to be sent to server.
 */
LWIP_STATIC void
sntp_initialize_request(struct sntp_msg *req)
{
  (void)memset_s(req, sizeof(struct sntp_msg), 0, SNTP_MSG_LEN);
  req->li_vn_mode = SNTP_VERSION | SNTP_MODE_CLIENT;

#if SNTP_CHECK_RESPONSE >= 2
  {
    u32_t sntp_time_sec, sntp_time_us;
    /* fill in transmit timestamp and save it in 'sntp_last_timestamp_sent' */
    SNTP_GET_SYSTEM_TIME(sntp_time_sec, sntp_time_us);
    sntp_last_timestamp_sent[0] = htonl(sntp_time_sec + (u32_t)(DIFF_SEC_1900_1970));
    req->transmit_timestamp[0] = sntp_last_timestamp_sent[0];
    /* we send/save us instead of fraction to be faster... */
    sntp_last_timestamp_sent[1] = htonl(sntp_time_us);
    req->transmit_timestamp[1] = sntp_last_timestamp_sent[1];
  }
#endif /* SNTP_CHECK_RESPONSE >= 2 */
}

/*
 * Retry: send a new request (and increase retry timeout).
 *
 * @param arg is unused (only necessary to conform to sys_timeout)
 */
LWIP_STATIC void
sntp_retry(void* arg)
{
  LWIP_UNUSED_ARG(arg);

  LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_retry: Next request will be sent in %"U32_F" ms\n",
    sntp_retry_timeout));

  /* set up a timer to send a retry and increase the retry delay */
  if(sys_timeout_ext(sntp_retry_timeout, sntp_request, NULL) == -1)
  {
      sntp_msg->err = ERR_MEM;
      sys_sem_signal(&sntp_msg->cb_completed);
      return;
  }

#if SNTP_RETRY_TIMEOUT_EXP
  {
    u32_t new_retry_timeout;
    /* increase the timeout for next retry */
    new_retry_timeout = sntp_retry_timeout << 1;
    /* limit to maximum timeout and prevent overflow */
    if ((new_retry_timeout <= SNTP_RETRY_TIMEOUT_MAX) &&
        (new_retry_timeout > sntp_retry_timeout)) {
      sntp_retry_timeout = new_retry_timeout;
    }
  }
#endif /* SNTP_RETRY_TIMEOUT_EXP */
}

/*
 * If Kiss-of-Death is received (or another packet parsing error),
 * try the next server or retry the current server and increase the retry
 * timeout if only one server is available.
 *
 * @param arg is unused (only necessary to conform to sys_timeout)
 */
LWIP_STATIC void
sntp_try_next_server(void* arg)
{
  LWIP_UNUSED_ARG(arg);

  if (NULL == sntp_pcb){
    return;
  }

  /* new server: reset retry timeout */
  SNTP_RESET_RETRY_TIMEOUT();
  sntp_current_server++;
  if (sntp_current_server >= sntp_num_servers) {
    /* Stop sntp module */
    sntp_stop();

    sntp_msg->err = ERR_TIMEOUT;
    sys_sem_signal(&sntp_msg->cb_completed);
    return;
  }
  LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_try_next_server: Sending request to server %"U16_F"\n",
    (u16_t)sntp_current_server));
  /* instantly send a request to the next server */
  sntp_request(NULL);
}

/* UDP recv callback for the sntp pcb */
LWIP_STATIC void
sntp_recv(void *arg, struct udp_pcb* pcb, struct pbuf *p, ip_addr_t *addr, u16_t port)
{
  u8_t mode = 0;
  u8_t stratum;
  u32_t receive_timestamp[SNTP_RECEIVE_TIME_SIZE];
  err_t err;

  LWIP_UNUSED_ARG(arg);
  LWIP_UNUSED_ARG(pcb);

  /* packet received: stop retry timeout  */
  sys_untimeout(sntp_try_next_server, NULL);
  sys_untimeout(sntp_request, NULL);

  err = ERR_ARG;
#if SNTP_CHECK_RESPONSE >= 1
  /* check server address and port */
  if (ip_addr_cmp(addr, &sntp_last_server_address) &&
    (port == SNTP_PORT))
#else /* SNTP_CHECK_RESPONSE >= 1 */
  LWIP_UNUSED_ARG(addr);
  LWIP_UNUSED_ARG(port);
#endif /* SNTP_CHECK_RESPONSE >= 1 */
  {
    /* process the response */
    if (p->tot_len == SNTP_MSG_LEN) {
      (void)pbuf_copy_partial(p, &mode, 1, SNTP_OFFSET_LI_VN_MODE);
      mode &= SNTP_MODE_MASK;
      /* if this is a SNTP response... */
      if ((mode == SNTP_MODE_SERVER) ||
          (mode == SNTP_MODE_BROADCAST)) {
        (void)pbuf_copy_partial(p, &stratum, 1, SNTP_OFFSET_STRATUM);
        if (stratum == SNTP_STRATUM_KOD) {
          /* Kiss-of-death packet. Use another server or increase UPDATE_DELAY. */
          err = SNTP_ERR_KOD;
          LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_recv: Received Kiss-of-Death\n"));
        } else {
#if SNTP_CHECK_RESPONSE >= 2
          /* check originate_timetamp against sntp_last_timestamp_sent */
          u32_t originate_timestamp[2];
          (void)pbuf_copy_partial(p, (void*)originate_timestamp, 8, SNTP_OFFSET_ORIGINATE_TIME);
          if ((originate_timestamp[0] != sntp_last_timestamp_sent[0]) ||
              (originate_timestamp[1] != sntp_last_timestamp_sent[1]))
          {
            LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid originate timestamp in response\n"));
          } else
#endif /* SNTP_CHECK_RESPONSE >= 2 */
          /* @todo: add code for SNTP_CHECK_RESPONSE >= 3 and >= 4 here */
          {
            /* correct answer */
            err = ERR_OK;
            (void)pbuf_copy_partial(p, (void *)receive_timestamp, SNTP_RECEIVE_TIME_SIZE * 4, SNTP_OFFSET_RECEIVE_TIME);
          }
        }
      } else {
        LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid mode in response: %"U16_F"\n", (u16_t)mode));
      }
    } else {
      LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid packet length: %"U16_F"\n", p->tot_len));
    }
  }
  (void)pbuf_free(p);
  if (err == ERR_OK) {
    /* Correct response, reset retry timeout */
    SNTP_RESET_RETRY_TIMEOUT();

    sntp_process(receive_timestamp, SNTP_RECEIVE_TIME_SIZE);
    /* Stop sntp module */
    sntp_stop();

    sntp_msg->err = (err_t) sntp_current_server;

    /* Return success */
    sys_sem_signal(&sntp_msg->cb_completed);

  } else if (err == SNTP_ERR_KOD) {
    /* Kiss-of-death packet. Use another server or increase UPDATE_DELAY. */
    sntp_try_next_server(NULL);
  } else {
    /* another error, try the same server again */
    sntp_retry(NULL);
  }
}

/* Actually send an sntp request to a server.
 *
 * @param server_addr resolved IP_add of the SNTP server
 */
LWIP_STATIC void
sntp_send_request(const ip_addr_t *server_addr)
{
  struct pbuf* p;

  /* if sntp_pcb has been release,that implies late response from DNS.. should be ignored */
  if (NULL == sntp_pcb){
    return;
  }

  p = pbuf_alloc(PBUF_TRANSPORT, SNTP_MSG_LEN, PBUF_RAM);
  if (p != NULL) {
    struct sntp_msg *sntpmsg = (struct sntp_msg *)p->payload;
    LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_send_request: Sending request to server\n"));
    /* initialize request message */
    sntp_initialize_request(sntpmsg);
    /* send request */
    if (udp_sendto(sntp_pcb, p, server_addr, SNTP_PORT))
    {
      LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_send_request: udp_sendto failed.\n"));
    }
    /* free the pbuf after sending it */
    (void)pbuf_free(p);
    /* set up receive timeout: try next server or retry on timeout */
    if (sntp_retry_count >= SNTP_MAX_REQUEST_RETRANSMIT )
    {
      sntp_retry_count = 0;
      LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_retry: Sending request to next server\n"));
      /* set up receive timeout: try next server or retry on timeout */
      if(sys_timeout_ext((u32_t)SNTP_RECV_TIMEOUT, sntp_try_next_server, NULL) == -1)
      {
          sntp_msg->err = ERR_MEM;
          sys_sem_signal(&sntp_msg->cb_completed);
          return;
      }
    }
    else
    {
      sntp_retry_count++;
      LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_retry: Sending request to server\n"));
      sntp_retry(NULL);
    }
#if SNTP_CHECK_RESPONSE >= 1
    /* save server address to verify it in sntp_recv */
    ip_addr_set_val(&sntp_last_server_address, server_addr);
#endif /* SNTP_CHECK_RESPONSE >= 1 */
  } else {
    LWIP_DEBUGF(SNTP_DEBUG_SERIOUS, ("sntp_send_request: Out of memory, trying again in %"U32_F" ms\n",
      (u32_t)SNTP_RETRY_TIMEOUT));
    /* out of memory: set up a timer to send a retry */
    if(sys_timeout_ext((u32_t)SNTP_RETRY_TIMEOUT, sntp_request, NULL) == -1)
    {
        sntp_msg->err = ERR_MEM;
        sys_sem_signal(&sntp_msg->cb_completed);
        return;
    }
  }
}

#if SNTP_SERVER_DNS
/*
 * DNS found callback when using DNS names as server address.
 */
LWIP_STATIC void
sntp_dns_found(const char* hostname, const ip_addr_t *ipaddr, u32_t count, void *arg)
{
  LWIP_UNUSED_ARG(hostname);
  LWIP_UNUSED_ARG(arg);
  LWIP_UNUSED_ARG(count);

  /* if sntp_pcb has been release,that implies late response from DNS.. should be ignored */
  if (NULL == sntp_pcb){
    return;
  }

  if (ipaddr != NULL) {
    /* Address resolved, send request */
    LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_dns_found: Server address resolved, sending request\n"));
    sntp_send_request(ipaddr);
  } else {
    /* DNS resolving failed -> try another server */
    LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_dns_found: Failed to resolve server address resolved, trying next server\n"));
    sntp_try_next_server(NULL);
  }
}
#endif /* SNTP_SERVER_DNS */

/*
 * Send out an sntp request.
 *
 * @param arg is unused (only necessary to conform to sys_timeout)
 */
LWIP_STATIC void
sntp_request(void *arg)
{
  ip_addr_t sntp_server_address;
  err_t err;

  #if SNTP_SERVER_DNS
  u32_t count = 1;
  #endif /* SNTP_SERVER_DNS */

  LWIP_UNUSED_ARG(arg);

  /* initialize SNTP server address */
#if SNTP_SERVER_DNS
  err = dns_gethostbyname(sntp_server_addresses[sntp_current_server], &sntp_server_address, &count,
                        sntp_dns_found, NULL);
  if (err == ERR_INPROGRESS) {
    /* DNS request sent, wait for sntp_dns_found being called */
    LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_request: Waiting for server address to be resolved.\n"));
    return;
  }
#else /* SNTP_SERVER_DNS */
  err = ipaddr_aton(sntp_server_addresses[sntp_current_server], &sntp_server_address)
    ? ERR_OK : ERR_ARG;

#endif /* SNTP_SERVER_DNS */

  if (err == ERR_OK) {
    sntp_send_request(&sntp_server_address);
  } else {
    /* address conversion failed, try another server */
    LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_request: Invalid server address, trying next server.\n"));
    if(sys_timeout_ext((u32_t)SNTP_RETRY_TIMEOUT, sntp_try_next_server, NULL) == -1)
    {
        sntp_msg->err = ERR_MEM;
        sys_sem_signal(&sntp_msg->cb_completed);
        return;
    }
  }
}

/*
 * Initialize this module.
 * Send out request instantly or after SNTP_STARTUP_DELAY.
 */
void
sntp_init(void *arg)
{
 struct api_sntp_msg * msg_in = (struct api_sntp_msg *)arg;
/* SNTP sanity checks */
#if ((SNTP_MAX_REQUEST_RETRANSMIT > 255) || (SNTP_MAX_REQUEST_RETRANSMIT < 0))
 #error "SNTP_MAX_REQUEST_RETRANSMIT should be between 0 to 255"
#endif

  if(sntp_pcb != NULL) {
    LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_init: In processing, please try later\n"));
    msg_in->err = ERR_INPROGRESS;
    sys_sem_signal(&msg_in->cb_completed);
    return;
  }

  sntp_msg = msg_in;

  sntp_current_server = 0;
  sntp_retry_count = 0;
  sntp_server_addresses = sntp_msg->sntp_server;
  sntp_num_servers = sntp_msg->server_num;

  SNTP_RESET_RETRY_TIMEOUT();
  sntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
  if (sntp_pcb == NULL) {
    sntp_msg->err = ERR_MEM;
    sys_sem_signal(&sntp_msg->cb_completed);
    return;
  }
  sntp_msg->err = ERR_OK;
  udp_recv(sntp_pcb, (udp_recv_fn)sntp_recv, NULL);
#if SNTP_STARTUP_DELAY
  if(sys_timeout_ext((u32_t)SNTP_STARTUP_DELAY, sntp_request, NULL) == -1)
  {
      sntp_msg->err = ERR_MEM;
      sys_sem_signal(&sntp_msg->cb_completed);
      return;
  }
#else
  sntp_request(NULL);
#endif
}

int lwip_sntp_start(int server_num, char **sntp_server, struct timeval *time_local)
{
  struct api_sntp_msg smsg;
  err_t err;

  if(server_num == 0 || sntp_server == NULL)
    return ERR_ARG;

  (void)memset_s(&smsg, sizeof(smsg), 0, sizeof(smsg));
  err = sys_sem_new(&smsg.cb_completed, 0);
  if (err != ERR_OK) {
    return ERR_MEM;
  }

  smsg.server_num = server_num;
  smsg.sntp_server = sntp_server;
  smsg.sntp_time  = time_local;
  err = tcpip_callback(sntp_init, (void *)&smsg);
  if (err != ERR_OK) {
    sys_sem_free(&smsg.cb_completed);
    return err;
  }
  err = (err_t)sys_arch_sem_wait(&smsg.cb_completed, 0);
  if (err == (err_t)SYS_ARCH_ERROR || err == (err_t)SYS_ARCH_TIMEOUT) {
    sys_sem_free(&smsg.cb_completed);
    return err;
  }
  sys_sem_free(&smsg.cb_completed);
  return smsg.err;
}


/*
 * Stop this module.
 */
LWIP_STATIC void
sntp_stop(void)
{
  if (sntp_pcb != NULL) {
    sys_untimeout(sntp_try_next_server, NULL);
    sys_untimeout(sntp_request, NULL);
    udp_remove(sntp_pcb);
    sntp_pcb = NULL;
  }
}

#endif /* LWIP_SNTP */


